/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: BUSL-1.1
*/

// inject renders Go source files with injected pinning values.
package inject

import (
	"errors"
	"io"
	"regexp"
	"text/template"
)

// Render renders the source code to inject the pinned values.
func Render(out io.Writer, vals PinningValues) error {
	if err := validate(vals); err != nil {
		return err
	}
	return sourceTpl.Execute(out, vals)
}

// validate validates the values to inject.
func validate(vals PinningValues) error {
	if vals.Package == "" {
		return errors.New("package is empty")
	}
	if vals.Ident == "" {
		return errors.New("identifier is empty")
	}
	if vals.Registry == "" {
		return errors.New("registry is empty")
	}
	if vals.Name == "" {
		return errors.New("name is empty")
	}
	if vals.Digest == "" {
		return errors.New("digest is empty")
	}

	if matched := packageNameRegexp.MatchString(vals.Package); !matched {
		return errors.New("package is not valid")
	}
	if matched := identRegexp.MatchString(vals.Ident); !matched {
		return errors.New("identifier is not valid")
	}
	if matched := registryRegexp.MatchString(vals.Registry); !matched {
		return errors.New("registry is not valid")
	}
	if matched := prefixRegexp.MatchString(vals.Prefix); !matched {
		return errors.New("prefix is not valid")
	}
	if matched := nameRegexp.MatchString(vals.Name); !matched {
		return errors.New("name is not valid")
	}
	if matched := tagRegexp.MatchString(vals.Tag); vals.Tag != "" && !matched {
		return errors.New("tag is not valid")
	}
	if matched := digestRegexp.MatchString(vals.Digest); !matched {
		return errors.New("digest is not valid")
	}

	return nil
}

// PinningValues contains the values to inject into the generated source code.
type PinningValues struct {
	// Package is the name of the package to generate.
	Package string
	// Ident is the base identifier of the generated constants.
	Ident string
	// Registry string
	Registry string
	// Prefix is the prefix of the container image name.
	Prefix string
	// Name is the name of the container image.
	Name string
	// Tag is the (optional) tag of the container image.
	Tag string
	// Digest is the digest of the container image.
	Digest string
}

var (
	sourceTpl = template.Must(template.New("goSource").Parse(goSourceTpl))

	// packageNameRegexp is the regular expression for a valid package name.
	packageNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)

	// identRegexp is the regular expression for a valid identifier.
	identRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)

	// registryRegexp is the regular expression for a valid registry.
	registryRegexp = regexp.MustCompile(`^[^\s/"]+$`)

	// prefixRegexp is the regular expression for a valid prefix.
	prefixRegexp = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9-_/]*)?$`)

	// nameRegexp is the regular expression for a valid name.
	nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-_]*$`)

	// tagRegexp is the regular expression for a valid tag.
	tagRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)

	// digestRegexp is the regular expression for a valid digest.
	digestRegexp = regexp.MustCompile(`^sha256:[a-f0-9]{64}$`)
)

const goSourceTpl = `package {{.Package}}

// Code generated by oci-pin. DO NOT EDIT.

const (
	// {{.Ident}}Registry is the {{.Name}} container image registry.
	{{.Ident}}Registry = "{{.Registry}}"

{{if .Prefix }}	// {{.Ident}}Prefix is the {{.Name}} container image prefix.
	{{.Ident}}Prefix = "{{.Prefix}}"

{{end}}	// {{.Ident}}Name is the {{.Name}} container image short name part.
	{{.Ident}}Name = "{{.Name}}"

{{if .Tag }}	// {{.Ident}}Tag is the tag for the {{.Name}} container image.
	{{.Ident}}Tag = "{{.Tag}}"

{{end}}	// {{.Ident}}Digest is the digest for the {{.Name}} container image.
	{{.Ident}}Digest = "{{.Digest}}"
)
`
